/**
 * Copyright 2021 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.util;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

/**
 * 泛型参数工具
 * 
 * @author jianggujin
 *
 */
public class JTypeParameterUtils {

    /**
     * 查找真实的类型参数
     * 
     * @param thisClass
     * @param parameterizedSuperclass
     * @param typeParamIndex
     * @return
     */
    public static Class<?> findTypeParameter(Class<?> thisClass, Class<?> parameterizedSuperclass, int typeParamIndex) {
        TypeVariable<?>[] typeParams = parameterizedSuperclass.getTypeParameters();
        String typeParamName = null;
        if (typeParamIndex > -1 && typeParamIndex < typeParams.length) {
            typeParamName = typeParams[typeParamIndex].getName();
        }
        // 未找到对应泛型参数
        if (typeParamName == null) {
            throw new IllegalStateException(
                    "unknown type parameter position'" + typeParamIndex + "': " + parameterizedSuperclass);
        }
        return findTypeParameter(thisClass, thisClass, parameterizedSuperclass, typeParamName, typeParamIndex);
    }

    /**
     * 查找真实的类型参数
     * 
     * @param thisClass
     * @param parameterizedSuperclass
     * @param typeParamName
     * @return
     */
    public static Class<?> findTypeParameter(Class<?> thisClass, Class<?> parameterizedSuperclass,
            String typeParamName) {
        int typeParamIndex = -1;
        TypeVariable<?>[] typeParams = parameterizedSuperclass.getTypeParameters();
        for (int i = 0; i < typeParams.length; i++) {
            if (typeParamName.equals(typeParams[i].getName())) {
                typeParamIndex = i;
                break;
            }
        }
        // 未找到对应泛型参数
        if (typeParamIndex < 0) {
            throw new IllegalStateException(
                    "unknown type parameter '" + typeParamName + "': " + parameterizedSuperclass);
        }
        return findTypeParameter(thisClass, thisClass, parameterizedSuperclass, typeParamName, typeParamIndex);
    }

    private static Class<?> findTypeParameter(Class<?> currentClass, Class<?> thisClass,
            Class<?> parameterizedSuperclass, String typeParamName, int typeParamIndex) {
        Class<?> superClass = currentClass.getSuperclass();
        if (superClass != null && parameterizedSuperclass.isAssignableFrom(superClass)) {
            if (superClass == parameterizedSuperclass) {
                return findTypeParameter(currentClass.getGenericSuperclass(), currentClass, thisClass,
                        parameterizedSuperclass, typeParamName, typeParamIndex);
            }
            return findTypeParameter(superClass, thisClass, parameterizedSuperclass, typeParamName, typeParamIndex);
        }
        Class<?>[] interfaces = currentClass.getInterfaces();
        for (int pos = 0; pos < interfaces.length; pos++) {
            Class<?> clazz = interfaces[pos];
            if (parameterizedSuperclass.isAssignableFrom(clazz)) {
                if (clazz == parameterizedSuperclass) {
                    return findTypeParameter(currentClass.getGenericInterfaces()[pos], currentClass, thisClass,
                            parameterizedSuperclass, typeParamName, typeParamIndex);
                }
                return findTypeParameter(clazz, thisClass, parameterizedSuperclass, typeParamName, typeParamIndex);
            }
        }
        return fail(thisClass, typeParamName);
    }

    private static Class<?> findTypeParameter(Type genericType, Class<?> currentClass, Class<?> thisClass,
            Class<?> parameterizedSuperclass, String typeParamName, int typeParamIndex) {
        if (!(genericType instanceof ParameterizedType)) {
            return Object.class;
        }

        Type[] actualTypeParams = ((ParameterizedType) genericType).getActualTypeArguments();

        Type actualTypeParam = actualTypeParams[typeParamIndex];
        if (actualTypeParam instanceof ParameterizedType) {
            actualTypeParam = ((ParameterizedType) actualTypeParam).getRawType();
        }
        if (actualTypeParam instanceof Class) {
            return (Class<?>) actualTypeParam;
        }
        if (actualTypeParam instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) actualTypeParam).getGenericComponentType();
            if (componentType instanceof ParameterizedType) {
                componentType = ((ParameterizedType) componentType).getRawType();
            }
            if (componentType instanceof Class) {
                return Array.newInstance((Class<?>) componentType, 0).getClass();
            }
        }
        if (actualTypeParam instanceof TypeVariable) {
            // Resolved type parameter points to another type parameter.
            TypeVariable<?> v = (TypeVariable<?>) actualTypeParam;
            currentClass = thisClass;
            if (!(v.getGenericDeclaration() instanceof Class)) {
                return Object.class;
            }

            parameterizedSuperclass = (Class<?>) v.getGenericDeclaration();
            typeParamName = v.getName();
            if (parameterizedSuperclass.isAssignableFrom(thisClass)) {
                return findTypeParameter(currentClass, thisClass, parameterizedSuperclass, typeParamName,
                        typeParamIndex);
            } else {
                return Object.class;
            }
        }
        return fail(thisClass, typeParamName);
    }

    private static Class<?> fail(Class<?> type, String typeParamName) {
        throw new IllegalStateException(
                "cannot determine the type of the type parameter '" + typeParamName + "': " + type);
    }
}
